package com.stripe.android.paymentsheet

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.stripe.android.cards.CardAccountRangeRepository
import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.lpmfoundations.luxe.SupportedPaymentMethod
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.lpmfoundations.paymentmethod.UiDefinitionFactory
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCode
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.paymentsheet.FormHelper.FormType
import com.stripe.android.paymentsheet.analytics.EventReporter
import com.stripe.android.paymentsheet.forms.FormArgumentsFactory
import com.stripe.android.paymentsheet.forms.FormFieldValues
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.model.paymentMethodType
import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments
import com.stripe.android.paymentsheet.ui.transformToPaymentMethodCreateParams
import com.stripe.android.paymentsheet.ui.transformToPaymentSelection
import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel
import com.stripe.android.ui.core.elements.AutomaticallyLaunchedCardScanFormDataHelper
import com.stripe.android.ui.core.elements.FORM_ELEMENT_SET_DEFAULT_MATCHES_SAVE_FOR_FUTURE_DEFAULT_VALUE
import com.stripe.android.uicore.elements.AutocompleteAddressInteractor
import com.stripe.android.uicore.elements.FormElement
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch

internal class DefaultFormHelper(
    private val coroutineScope: CoroutineScope,
    private val linkInlineHandler: LinkInlineHandler,
    private val cardAccountRangeRepositoryFactory: CardAccountRangeRepository.Factory,
    private val paymentMethodMetadata: PaymentMethodMetadata,
    private val newPaymentSelectionProvider: () -> NewPaymentOptionSelection?,
    private val selectionUpdater: (PaymentSelection?) -> Unit,
    private val linkConfigurationCoordinator: LinkConfigurationCoordinator?,
    private val setAsDefaultMatchesSaveForFutureUse: Boolean,
    private val eventReporter: EventReporter,
    private val savedStateHandle: SavedStateHandle,
    private val autocompleteAddressInteractorFactory: AutocompleteAddressInteractor.Factory?,
    private val isLinkUI: Boolean = false,
    private val automaticallyLaunchedCardScanFormDataHelper: AutomaticallyLaunchedCardScanFormDataHelper?,
) : FormHelper {
    companion object {
        internal const val PREVIOUSLY_COMPLETED_PAYMENT_FORM = "previously_completed_payment_form"
        fun create(
            viewModel: BaseSheetViewModel,
            paymentMethodMetadata: PaymentMethodMetadata,
            linkInlineHandler: LinkInlineHandler = LinkInlineHandler.create(),
            shouldCreateAutomaticallyLaunchedCardScanFormDataHelper: Boolean = false,
        ): FormHelper {
            return DefaultFormHelper(
                coroutineScope = viewModel.viewModelScope,
                linkInlineHandler = linkInlineHandler,
                cardAccountRangeRepositoryFactory = viewModel.cardAccountRangeRepositoryFactory,
                paymentMethodMetadata = paymentMethodMetadata,
                newPaymentSelectionProvider = {
                    viewModel.newPaymentSelection
                },
                linkConfigurationCoordinator = viewModel.linkHandler.linkConfigurationCoordinator,
                selectionUpdater = {
                    viewModel.updateSelection(it)
                },
                setAsDefaultMatchesSaveForFutureUse = viewModel.customerStateHolder.paymentMethods.value.isEmpty(),
                eventReporter = viewModel.eventReporter,
                savedStateHandle = viewModel.savedStateHandle,
                autocompleteAddressInteractorFactory = viewModel.autocompleteAddressInteractorFactory,
                automaticallyLaunchedCardScanFormDataHelper =
                if (shouldCreateAutomaticallyLaunchedCardScanFormDataHelper) {
                    val hasSeenAutomaticCardScanLaunch =
                        viewModel.newPaymentSelection?.paymentSelection is PaymentSelection.New.Card &&
                            viewModel.newPaymentSelection?.getPaymentMethodCreateParams() != null

                    AutomaticallyLaunchedCardScanFormDataHelper(
                        openCardScanAutomaticallyConfig = paymentMethodMetadata.openCardScanAutomatically,
                        savedStateHandle = viewModel.savedStateHandle,
                        hasAutomaticallyLaunchedCardScanInitialValue = hasSeenAutomaticCardScanLaunch,
                    )
                } else {
                    null
                },
            )
        }

        fun create(
            coroutineScope: CoroutineScope,
            cardAccountRangeRepositoryFactory: CardAccountRangeRepository.Factory,
            autocompleteAddressInteractorFactory: AutocompleteAddressInteractor.Factory?,
            paymentMethodMetadata: PaymentMethodMetadata,
            eventReporter: EventReporter,
            savedStateHandle: SavedStateHandle,
            isLinkUI: Boolean = false,
        ): FormHelper {
            return DefaultFormHelper(
                coroutineScope = coroutineScope,
                linkInlineHandler = LinkInlineHandler.create(),
                cardAccountRangeRepositoryFactory = cardAccountRangeRepositoryFactory,
                paymentMethodMetadata = paymentMethodMetadata,
                newPaymentSelectionProvider = { null },
                linkConfigurationCoordinator = null,
                selectionUpdater = {},
                setAsDefaultMatchesSaveForFutureUse = FORM_ELEMENT_SET_DEFAULT_MATCHES_SAVE_FOR_FUTURE_DEFAULT_VALUE,
                eventReporter = eventReporter,
                savedStateHandle = savedStateHandle,
                autocompleteAddressInteractorFactory = autocompleteAddressInteractorFactory,
                isLinkUI = isLinkUI,
                automaticallyLaunchedCardScanFormDataHelper = null
            )
        }
    }

    private val lastFormValues = MutableSharedFlow<Pair<FormFieldValues?, String>>()

    private val paymentSelection: Flow<PaymentSelection?> = combine(
        lastFormValues,
        linkInlineHandler.linkInlineState,
    ) { formValues, inlineSignupViewState ->
        formValues.first?.transformToPaymentSelection(
            paymentMethod = supportedPaymentMethodForCode(formValues.second),
            paymentMethodMetadata = paymentMethodMetadata,
            inlineSignupViewState = inlineSignupViewState,
        )
    }

    private var previouslyCompletedForm: PaymentMethodCode?
        get() = savedStateHandle[PREVIOUSLY_COMPLETED_PAYMENT_FORM]
        set(value) {
            savedStateHandle[PREVIOUSLY_COMPLETED_PAYMENT_FORM] = value
        }

    init {
        coroutineScope.launch {
            paymentSelection.collect { selection ->
                selectionUpdater(selection)
                reportFieldCompleted(selection?.paymentMethodType)
            }
        }
    }

    override fun formElementsForCode(code: String): List<FormElement> {
        val currentSelection = newPaymentSelectionProvider()?.takeIf { it.getType() == code }

        return paymentMethodMetadata.formElementsForCode(
            code = code,
            uiDefinitionFactoryArgumentsFactory = UiDefinitionFactory.Arguments.Factory.Default(
                cardAccountRangeRepositoryFactory = cardAccountRangeRepositoryFactory,
                linkConfigurationCoordinator = linkConfigurationCoordinator,
                linkInlineHandler = linkInlineHandler,
                onLinkInlineSignupStateChanged = linkInlineHandler::onStateUpdated,
                paymentMethodCreateParams = currentSelection?.getPaymentMethodCreateParams(),
                paymentMethodOptionsParams = currentSelection?.getPaymentMethodOptionParams(),
                paymentMethodExtraParams = currentSelection?.getPaymentMethodExtraParams(),
                initialLinkUserInput = when (val selection = currentSelection?.paymentSelection) {
                    is PaymentSelection.New.Card -> selection.linkInput
                    else -> null
                },
                previousLinkSignupCheckboxSelection = when (val selection = currentSelection?.paymentSelection) {
                    // User entered a card and may have link input
                    is PaymentSelection.New.Card -> selection.linkInput != null
                    else -> null // Not a card, so no previous choice
                },
                setAsDefaultMatchesSaveForFutureUse = setAsDefaultMatchesSaveForFutureUse,
                autocompleteAddressInteractorFactory = autocompleteAddressInteractorFactory,
                isLinkUI = isLinkUI,
                automaticallyLaunchedCardScanFormDataHelper = automaticallyLaunchedCardScanFormDataHelper,
            ),
        ) ?: emptyList()
    }

    override fun createFormArguments(
        paymentMethodCode: PaymentMethodCode,
    ): FormArguments {
        return FormArgumentsFactory.create(
            paymentMethodCode = paymentMethodCode,
            metadata = paymentMethodMetadata,
        )
    }

    override fun onFormFieldValuesChanged(formValues: FormFieldValues?, selectedPaymentMethodCode: String) {
        coroutineScope.launch {
            lastFormValues.emit(formValues to selectedPaymentMethodCode)
        }
    }

    override fun getPaymentMethodParams(
        formValues: FormFieldValues?,
        selectedPaymentMethodCode: String
    ): PaymentMethodCreateParams? {
        return formValues?.transformToPaymentMethodCreateParams(
            paymentMethodCode = selectedPaymentMethodCode,
            paymentMethodMetadata = paymentMethodMetadata
        )
    }

    private fun requiresFormScreen(paymentMethodCode: String, formElements: List<FormElement>): Boolean {
        val userInteractionAllowed = formElements.any { it.allowsUserInteraction }
        return userInteractionAllowed ||
            paymentMethodCode == PaymentMethod.Type.USBankAccount.code ||
            paymentMethodCode == PaymentMethod.Type.Link.code
    }

    override fun formTypeForCode(paymentMethodCode: PaymentMethodCode): FormType {
        val formElements = formElementsForCode(paymentMethodCode)
        return if (requiresFormScreen(paymentMethodCode, formElements)) {
            FormType.UserInteractionRequired
        } else {
            val mandate = formElements.firstNotNullOfOrNull { it.mandateText }
            if (mandate == null) {
                FormType.Empty
            } else {
                FormType.MandateOnly(mandate)
            }
        }
    }

    private fun supportedPaymentMethodForCode(code: String): SupportedPaymentMethod {
        return requireNotNull(paymentMethodMetadata.supportedPaymentMethodForCode(code = code))
    }

    private fun reportFieldCompleted(code: PaymentMethodCode?) {
        if (code == null || formTypeForCode(code) != FormType.UserInteractionRequired) {
            return
        }
        /*
         * Prevents this event from being reported multiple times on field interactions
         * on the same payment form. We should have one field interaction event for
         * every form shown event triggered.
         */
        if (previouslyCompletedForm != code) {
            eventReporter.onPaymentMethodFormCompleted(code)
            previouslyCompletedForm = code
        }
    }
}
